
#region using Statements

using System;
using System.Collections.Specialized;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Xml;
using System.Web;
using System.Web.Services.Protocols;

#endregion

namespace Windows.Live.SSO
{
    public class LiveSLT
    {

        #region Declarations and Constructor

        private string _siteID;
        private int _loginSeconds;
        private string _thumbprint;
        private string _webProxy;

        public bool Success = false;
        public string ExceptionCode = String.Empty;
        public string ExceptionMessage = String.Empty;
        public string InnerExceptionCode = String.Empty;
        public string InnerExceptionMessage = String.Empty;

        private const string CredentialWebServiceUrl = "https://ppsacredential.service.passport.net/pksecure/PPSACredentialPK.srf?id={0}";

        public LiveSLT(string ThumbPrint, string SiteID, string WebProxyName, int LoginSeconds)
        {
            _siteID = SiteID;
            _loginSeconds = LoginSeconds;
            _thumbprint = ThumbPrint.Replace(" ", String.Empty);
            _webProxy = WebProxyName;
        }

        #endregion

        #region LoadCertificate

        private X509Certificate2 LoadCertificate()
        {
            return LoadCertificate(_thumbprint);
        }
        public static X509Certificate2 LoadCertificate(string ThumbPrint)
        {
            HttpContext webContext = HttpContext.Current;
            
            // Retrieve the Local Machine certificate store and load the x.509
            // client certificate, using the certificate's thumbprint to 
            // identify and retrieve it from the store.
            try
            {
                webContext.Application.Lock();

                X509Store x509Store = new X509Store(StoreLocation.LocalMachine);
                X509Certificate2Collection x509Collection;
                x509Store.Open(OpenFlags.ReadOnly);
                x509Collection = x509Store.Certificates.Find(X509FindType.FindByThumbprint, ThumbPrint, false);
                x509Store.Close();
                if ((x509Collection.Count == 1))
                {
                    X509Certificate2 certificate = x509Collection[0];

                    try
                    {
                        AsymmetricAlgorithm privateKey = certificate.PrivateKey;
                    }
                    catch
                    {
                        throw new ApplicationException(String.Format("Private key cannot be read for the certificate with thumbprint: {0}.  Ensure WinHTTPCertCfg.exe has been used to assign permissions to the IIS Application Pool Identity (defaults to Network Service).", ThumbPrint));
                    }

                    webContext.Application["Certificate"] = certificate.GetRawCertData();
                    return certificate;
                }
                else
                {
                    throw new ApplicationException("Certificate not found!");
                }
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("Private key cannot be read"))
                {
                    throw ex;
                }
                throw new ApplicationException(String.Format("Unable to retreive certificate with thumbprint: {0}", ThumbPrint), ex);
            }
            finally
            {
                try
                {
                    webContext.Application.UnLock();
                }
                catch { }
            }
        }

        #endregion

        #region GetSLT

        public string GetSLT(string UserName, string siteId)
        {
            try
            {
                CredentialServiceAPISoapServer pkiService = new CredentialServiceAPISoapServer();
                pkiService.Url = String.Format(CredentialWebServiceUrl, siteId);

                if (!String.IsNullOrEmpty(_webProxy))
                {
                    WebProxy proxyObject = new WebProxy(_webProxy, true);
                    pkiService.Proxy = proxyObject;
                }

                pkiService.WSSecurityHeader = new tagWSSECURITYHEADER();
                pkiService.WSSecurityHeader.version = EnumSHVersion.eshHeader25;

                StringBuilder headerBuilder = new StringBuilder();
                headerBuilder.Append("<s:ppSoapHeader xmlns:s=\"http://schemas.microsoft.com/Passport/SoapServices/SoapHeader\" version=\"1.0\">");
                headerBuilder.Append("<s:lcid>1033</s:lcid>");
                headerBuilder.Append("<s:sitetoken><t:siteheader xmlns:t=\"http://schemas.microsoft.com/Passport/SiteToken\" id=\"");
                headerBuilder.Append(this._siteID);
                headerBuilder.Append("\" /></s:sitetoken>");
                headerBuilder.Append("</s:ppSoapHeader>");
                pkiService.WSSecurityHeader.ppSoapHeader25 = headerBuilder.ToString();

                // Attaching certificate
                pkiService.ClientCertificates.Add(LoadCertificate());

                // Create the passID
                tagPASSID passID = new tagPASSID();
                passID.pit = PASSIDTYPE.PASSID_SIGNINNAME;
                passID.bstrID = UserName;

                // Call web-service and get SLT
                pkiService.PreAuthenticate = true;
                pkiService.UnsafeAuthenticatedConnectionSharing = true;
                string slt = pkiService.GetSLT(passID, this._loginSeconds);

                this.Success = true;
                this.ExceptionCode = String.Empty;
                this.ExceptionMessage = String.Empty;
                this.InnerExceptionCode = String.Empty;
                this.InnerExceptionMessage = String.Empty;

                return slt;
            }
            catch (SoapException ex)
            {
                this.Success = false;
                this.ExceptionCode = String.Empty;
                this.ExceptionMessage = String.Empty;
                this.InnerExceptionCode = String.Empty;
                this.InnerExceptionMessage = String.Empty;

                try
                {
                    XmlDataDocument doc = new XmlDataDocument();
                    doc.LoadXml(ex.Detail.InnerXml);
                    XmlNamespaceManager psfNamespace = new XmlNamespaceManager(doc.NameTable);
                    psfNamespace.AddNamespace("psf", @"http://schemas.microsoft.com/Passport/SoapServices/SOAPFault");

                    this.ExceptionCode = doc.SelectSingleNode("/psf:error/psf:value", psfNamespace).InnerText.Trim();
                    this.ExceptionMessage = doc.SelectSingleNode("/psf:error/psf:description/psf:text", psfNamespace).InnerText.Trim();
                    this.InnerExceptionCode = doc.SelectSingleNode("/psf:error/psf:internalerror/psf:code", psfNamespace).InnerText.Trim();
                    this.InnerExceptionMessage = doc.SelectSingleNode("/psf:error/psf:internalerror/psf:text", psfNamespace).InnerText.Trim();
                }
                catch (Exception ex2)
                {
                    this.ExceptionCode = ex.Code.ToString();
                    this.ExceptionMessage = ex.Detail.OuterXml;
                    this.InnerExceptionCode = String.Empty;
                    this.InnerExceptionMessage = String.Empty;
                }
            }
            catch (Exception ex)
            {
                this.Success = false;
                this.ExceptionCode = String.Empty;
                this.ExceptionMessage = ex.Message;
                this.InnerExceptionCode = String.Empty;
                this.InnerExceptionMessage = String.Empty;
            }

            return String.Empty;
        }
        #endregion

        #region CustomExceptionMessage

        public string CustomExceptionMessage
        {
            get
            {
                if (ExceptionMessage.ToLower().Contains("remote name could not be resolved"))
                {
                    return ExceptionMessage + ".  Please verify internet connectivity from the web server and a valid webProxy in the web.config.";
                }
                if (ExceptionMessage.ToLower().Contains("unable to retreive certificate"))
                {
                    return ExceptionMessage + ".  Please verify the thumbprint of the certificate in the web.config file.";
                }
                if (ExceptionMessage.ToLower().Contains("could not create ssl/tls secure channel"))
                {
                    return ExceptionMessage + ".  Please make sure you have run winhttpcertcfg.exe on the certificate to grant permissions to the proper account used for the IIS Application Pool.";
                }

                //site cannot be authenticated
                switch (InnerExceptionCode.ToLower())
                {
                    case "0xcffffc15":
                        return "The specified Windows Live ID does not exist.  The account has not been provisioned by your system administrator.";
                    case "0x80045024":
                        return ExceptionMessage + ".  The Certificate & Site ID combination for this SSO application does not have permission to the specified Windows Live ID.  The ID is either in an untrusted domain or has not been imported into the trusted domain (EASI ID).";
                    case "0x80044021":
                        return InnerExceptionMessage + "  The Certificate & Site ID combination for this SSO application is invalid.";
                }

                if (!String.IsNullOrEmpty(InnerExceptionMessage))
                {
                    return ExceptionMessage + ".  " + InnerExceptionMessage;
                }
                return ExceptionMessage;
            }
        }
        #endregion
    }
}